package eztools

import (
	"database/sql"
	"reflect"
	"strconv"

	"github.com/go-ldap/ldap/v3"
	// mysql always imported for invokers
	_ "github.com/go-sql-driver/mysql"
	"github.com/sanbornm/go-selfupdate/selfupdate"
)

const (
	strDBSQL   = "mysql"
	strCfgFile = "eztools"
)

// Db unified struct for all kinds of dbs, sql only now.
type Db struct {
	*sql.DB
	tblDef, fldID, fldStr string
}

// CfgDb cfg db
type CfgDb struct {
	// Cmt = comments
	Cmt string `xml:",comment"`
	// Usr name of the user element for DB
	Usr string `xml:"user"`
	// Pas name of the password element for DB
	Pas string `xml:"pass"`
	// IP name of the IP element for DB
	IP string `xml:"ip"`
	// Db name of the database element
	Db string `xml:"name"`
	// TblDef name of the default table for DB
	TblDef string `xml:"tblDef"`
	// Tex = text. not used
	Tex string `xml:",chardata"`
}

// MakeDbWtCfgs connects to the database using paramters,
//
//	and initialize variables with table tblDef
//
// Return value: error = ErrIncomplete when tblDef does not provide
//
//	information for variables. use SetFldDef to provide it afterwards.
func MakeDbWtCfgs(user, pass, ip, database, tblDef string) (*Db, error) {
	if len(user) < 1 || len(database) < 1 || len(ip) < 1 || len(pass) < 1 {
		return nil, ErrInvalidInput
	}

	db, err := sql.Open(strDBSQL, user+":"+pass+"@"+ip+"/"+database)
	if err != nil {
		return nil, err
	}
	/*err = db.Ping()
	if err != nil {
		return nil, err
	}*/
	dbs := Db{db, tblDef, "", ""}
	if len(tblDef) < 1 {
		return &dbs, ErrIncomplete
	}
	// get primary key
	res, err := dbs.Search("INFORMATION_SCHEMA.KEY_COLUMN_USAGE",
		"TABLE_NAME=\""+tblDef+"\" AND CONSTRAINT_NAME=\"PRIMARY\"",
		[]string{"COLUMN_NAME"}, "")
	if err != nil {
		return &dbs, err
	}
	if res == nil {
		return &dbs, ErrIncomplete
	}
	dbs.fldID = res[0][0]
	_, res1, err := dbs.Describe(tblDef)
	if err != nil {
		return &dbs, err
	}
	if res1 == nil || len(res1) < 2 {
		return &dbs, ErrIncomplete
	}
	// only two fields recognized, in order of id and str
	if dbs.fldID == res1[0] {
		dbs.fldStr = res1[1]
	} else {
		dbs.fldStr = res1[0]
	}
	return &dbs, nil
}

// MakeDbWtCfgFile connects to the database using paramters from {path}/{file}
// Parameters:	path defaults to ${HOME} and .
//
//	file defaults to "eztools.xml"
func MakeDbWtCfgFile(path, file string) (*Db, Cfg, error) {
	var cfg Cfg
	if _, err := XMLReadDefault(path, file, &cfg); err != nil {
		return nil, cfg, err
	}
	db, err := MakeDbWtCfgs(cfg.Db.Usr, cfg.Db.Pas, cfg.Db.IP, cfg.Db.Db, cfg.Db.TblDef)
	return db, cfg, err
}

// MakeDb connects to the database using parameters from eztools.xml
// root element is named "root", elements include "ip", "db", "user" and "pass"
func MakeDb() (*Db, Cfg, error) {
	return MakeDbWtCfgFile("", strCfgFile)
}

/*func (db Dbs) Close() {
	db.Close()
}*/

// SetFldDef sets id & str field names of TblDef,
//
//	if not got automatically.
func (db *Db) SetFldDef(id, str string) {
	db.fldID = id
	db.fldStr = str
}

// GetFldID gets field id value
func (db Db) GetFldID() string {
	return db.fldID
}

// GetFldStr gets field str value
func (db Db) GetFldStr() string {
	return db.fldStr
}

// GetTblDef gets value of a tblDef config
func (db Db) GetTblDef() string {
	return db.tblDef
}

func makeWhere(cri string) string {
	if len(cri) > 0 {
		return " WHERE " + cri
	}
	return ""
}

// SQLRows2Strings returns arrays from rows
func SQLRows2Strings(rows *sql.Rows) (res [][]string, err error) {
	col, err := rows.Columns()
	if err != nil {
		return
	}
	colLen := len(col)
	rawRes := make([][]byte, colLen)
	dest := make([]interface{}, colLen)
	for i := range rawRes {
		dest[i] = &rawRes[i]
	}
	for rows.Next() {
		err = rows.Scan(dest...)
		if err != nil {
			err = ErrIncomplete
			if Debugging {
				LogPrint(err)
			}
		} else {
			row1 := make([]string, 0)
			for _, raw := range rawRes {
				row1 = append(row1, string(raw))
			}
			res = append(res, row1)
		}
	}
	return
}

func sqlLogRows(rows *sql.Rows) ([][]string, error) {

	res, err := SQLRows2Strings(rows)
	if Debugging && Verbose > 2 {
		LogPrint("selected result begins.")
		LogPrint(res)
		LogPrint("selected result ends.")
	}
	return res, err
}

// Search gets values of fields "sel" from "table", using "cri" as "WHERE",
//
//	with "more" appended to SQL command.
//
// Parameters: more: will not be prefixed with space automatically
func (db Db) Search(table string, cri string, sel []string, more string) ([][]string, error) {
	var selStr string
	if sel != nil {
		i := 0
		selStr = sel[i]
		for i++; i < len(sel); i++ {
			selStr += "," + sel[i]
		}
	} else {
		selStr = "*"
	}
	if Debugging {
		if Verbose > 1 {
			LogPrint("SELECT " + selStr + " from " + table + makeWhere(cri) + more)
		}
	}
	rows, err := db.Query("SELECT " + selStr + " from " + table + makeWhere(cri) + more)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return sqlLogRows(rows)
}

// UpdateWtParams updates "fields" in "table" with "values", using "cri" as "WHERE".
// Parameter yes2all = no confirmation in debug mode. Always no confirmation in non-debug mode.
// Return values:
//
//	ErrInvalidInput if no fields/values as parameters
//	ErrAbort if user chooese no when yes2all is true
//	ErrNoValidResults if no update command can be constructed
//	other errors from sql
func (db Db) UpdateWtParams(table, cri string, fields, values []string, neglectEmpty, yes2all bool) error {
	if len(fields) < 1 || len(values) < 1 {
		return ErrInvalidInput
	}
	var u string
	for i := 0; i < len(fields) && i < len(values); i++ {
		if neglectEmpty && len(values[i]) == 0 {
			continue
		}
		if len(u) > 0 {
			u = u + ", "
		}
		u += fields[i] + "="
		if values[i] != "NULL" {
			u = u + "\""
		}
		u = u + values[i]
		if values[i] != "NULL" {
			u = u + "\""
		}
	}
	if len(u) == 0 {
		return ErrNoValidResults
	}
	if Debugging && !yes2all {
		LogPrint("UPDATE " + table + " SET " + u + makeWhere(cri))
		if !ChkCfmNPrompt("continue", "n") {
			return ErrAbort
		}
	}
	_, err := db.Exec("UPDATE " + table + " SET " + u + makeWhere(cri))
	return err
}

// AddWtParamsUniq adds "values" to "fields", if no duplicate records in existence.
// Parameter yes2all = no confirmation in debug mode. Always no confirmation in non-debug mode.
func (db Db) AddWtParamsUniq(table string, fields []string, values []string, yes2all bool) (int, error) {
	var cri string
	for i := 0; i < len(fields) && i < len(values); i++ {
		if i != 0 {
			cri += " AND "
		}
		cri += fields[i] + "=" + values[i]
	}
	if len(cri) < 1 {
		return InvalidID, ErrInvalidInput
	}
	//Does ID field exist, to return it when in existence?
	_, err := db.Search(table, "", []string{db.fldID}, "")
	idExists := true
	var fields2Chk []string
	if err != nil {
		//TODO: check which err it is
		idExists = false
		fields2Chk = make([]string, 1)
		fields2Chk[0] = db.fldID
	} else {
		fields2Chk = fields
	}
	searched, err := db.Search(table, cri, fields2Chk, "")
	if err != nil {
		if idExists && len(searched) > 0 {
			ret, err := strconv.Atoi(searched[0][0])
			if err == nil {
				return ret, ErrInExistence
			}
		}
		return InvalidID, ErrInExistence
	}
	if len(searched) > 0 {
		return InvalidID, ErrNoValidResults
	}
	return db.AddWtParams(table, fields, values, yes2all)
}

// AddWtParams adds "values" to "fields", no matter whether duplicate records in existence.
// Parameter yes2all = no confirmation in debug mode. Always no confirmation in non-debug mode.
func (db Db) AddWtParams(table string, fields []string, values []string, yes2all bool) (int, error) {
	if len(fields) < 1 || len(values) < 1 {
		return 0, ErrInvalidInput
	}
	f := fields[0]
	v := "\"" + values[0] + "\""
	for i := 1; i < len(fields) && i < len(values); i++ {
		f = f + ", " + fields[i]
		v = v + ", \"" + values[i] + "\""
	}
	if Debugging && !yes2all {
		LogPrint("INSERT INTO " + table + " (" + f + ") VALUES (" + v + ")")
		if !ChkCfmNPrompt("continue", "n") {
			return DefID, ErrAbort
		}
	}
	res, err := db.Exec("INSERT INTO " + table + " (" + f + ") VALUES (" + v + ")")
	var id int
	if err != nil {
		return InvalidID, err
	}
	var num int64
	num, err = res.LastInsertId()
	if err != nil {
		return InvalidID, err
	}
	id = (int)(num)
	return id, err
}

// DeleteWtParams deletes items with specified WHERE clause
func (db Db) DeleteWtParams(table, where string, yes2all bool) error {
	cmd := "DELETE FROM " + table + makeWhere(where)
	if Debugging && !yes2all {
		LogPrint(cmd)
		if !ChkCfmNPrompt("continue", "n") {
			return ErrAbort
		}
	}
	_, err := db.Exec(cmd)
	return err
}

// DeleteWtID by ID
func (db Db) DeleteWtID(table, id string) error {
	return db.DeleteWtParams(table, db.fldID+"="+id, false)
}

// Describe returns all column information
// e.g., [[id char(50) NO PRI] [ str tinytext YES ]]
// Return values: all info, column names, error
func (db Db) Describe(table string) ([][]string, []string, error) {
	rows, err := db.Query("DESCRIBE " + table)
	if err != nil {
		return nil, nil, err
	}
	res, err := sqlLogRows(rows)
	var arr []string
	for _, i := range res {
		arr = append(arr, i[0])
	}
	return res, arr, err
}

// GetSortedPairsIntStr returns all sorted Pairs[int,string] from input table
func (db Db) GetSortedPairsIntStr(table string) (ps *Pairs[int, string], err error) {
	ps = new(Pairs[int, string])
	var (
		id  int
		val string
	)
	err = db.selectSortedPairs(table, &id, &val,
		func(id, val interface{}, err error) {
			defer Log(err)
			if err != nil {
				if Debugging {
					LogPrint(err)
				}
				return
			}
			i, ok := id.(*int)
			if !ok {
				if Debugging {
					LogPrint(reflect.TypeOf(id).String(), "not *int in pair")
				}
				return
			}
			v, ok := val.(*string)
			if !ok {
				if Debugging {
					LogPrint(reflect.TypeOf(val).String(), "not *string in pair")
				}
				return
			}
			ps.Add(*i, *v)
		})
	return
}

type selectSortedPairsFunc func(interface{}, interface{}, error)

func (db Db) selectSortedPairs(table string, id, val interface{},
	fn selectSortedPairsFunc) (err error) {
	cmd := "SELECT " + db.fldID + "," + db.fldStr + " from " + table + " order by " + db.fldID
	if Debugging {
		if Verbose > 2 {
			LogPrint(cmd)
		}
	}
	rows, err := db.Query(cmd)
	if err != nil {
		return
	}
	defer rows.Close()
	err = ErrNoValidResults
	for rows.Next() {
		err = rows.Scan(id, val)
		fn(id, val, err)
	}
	return
}

// GetPairIDFromInt gets ID (int) from value (int)
// When multiple results got, the first one will be taken.
func (db Db) GetPairIDFromInt(table string, val int) (int, error) {
	return db.GetPairID(table, strconv.Itoa(val))
}

// GetPairID gets ID (int) from value (string)
// When multiple results got, the first one will be taken.
func (db Db) GetPairID(table string, str string) (int, error) {
	ret, err := db.GetPair(table, str, db.fldStr, db.fldID)
	if err != nil {
		return 0, err
	}
	return strconv.Atoi(ret)
}

// GetPairInt gets value (int) from ID (string)
func (db Db) GetPairInt(table string, id string) (int, error) {
	res, err := db.GetPairStr(table, id)
	if err != nil {
		return InvalidID, err
	}
	return strconv.Atoi(res)
}

// GetPairStrFromInt gets value (string) from ID (int)
func (db Db) GetPairStrFromInt(table string, id int) (string, error) {
	return db.GetPairStr(table, strconv.Itoa(id))
}

// GetPairStr gets value (string) from ID (string)
func (db Db) GetPairStr(table string, id string) (string, error) {
	return db.GetPair(table, id, db.fldID, db.fldStr)
}

// GetPair gets "to" field whose "from" field equals "id" in "table"
// When multiple results got, the first one will be taken.
// return value error = from db.Query;
//
//	ErrNoValidResults when no valid results got,
//	                  and LogErrPrint will be called.
func (db Db) GetPair(table, id, from, to string) (string, error) {
	cmd := "SELECT " + to + " from " + table + " where " + from + "=\"" + id + "\""
	if Debugging {
		if Verbose > 2 {
			LogPrint(cmd)
		}
	}
	rows, err := db.Query(cmd)
	if err != nil {
		return "", err
	}
	defer rows.Close()
	var str string
	if Debugging && Verbose > 2 {
		var res [][]string
		res, err = sqlLogRows(rows)
		if err == nil {
			if res != nil {
				str = res[0][0]
			} else {
				err = ErrNoValidResults
			}
		}
	} else {
		err = ErrNoValidResults
		for rows.Next() {
			err = rows.Scan(&str)
			break
		}
	}
	//if err != nil {
	//LogErrPrint(err)
	//}
	return str, err
}

// Locate gets ID (int) from value (string)
// Deprecated: Use GetPairID instead.
func (db Db) Locate(table string, str string) (id int, err error) {
	var rows *sql.Rows
	rows, err = db.Query("SELECT " + db.fldID + " from " + table +
		" where " + db.fldStr + "=\"" + str + "\" order by " + db.fldID)
	if err != nil {
		return DefID, err
	}
	defer rows.Close()
	for rows.Next() {
		err = rows.Scan(&id)
		if err != nil {
			if Debugging && Verbose > 1 {
				LogPrint(err)
			}
		}
	}
	return id, nil
}

// choosePairasks user to choose from a table by ID - value pairs
//
//	and allows user to add one, returning the ID user has chosen
func choosePair(table string, pi Pairs[int, string]) (int, error) {
	res := ChooseStringsWtIDs(
		func() int {
			return pi.Len()
		},
		func(i int) int {
			ret, _, _ := pi.Get(i)
			return ret
		},
		func(i int) string {
			_, ret, _ := pi.Get(i)
			return ret
		}, "Choose for "+table)
	return res, nil
}

// ChoosePair asks user to choose from a table by ID - value pairs.
// Return value: InvalidID and nil, for invalid input
func (db Db) ChoosePair(table string, allAllowed bool) (int, error) {
	pi, err := db.GetSortedPairsIntStr(table)
	if err != nil {
		return InvalidID, err
	}
	if allAllowed {
		pi.Add(AllID, " <Affect All Items>")
	}
	return choosePair(table, *pi)
}

// ChoosePairOrAdd asks user to choose from a table by ID - value pairs,
//
//	allowing to add one new.
//
// Return value: InvalidID and nil, for invalid input
func (db Db) ChoosePairOrAdd(table string, allAllowed, uniq bool) (int, error) {
	pi, err := db.GetSortedPairsIntStr(table)
	if err != nil {
		return InvalidID, err
	}
	if allAllowed {
		pi.Add(AllID, " <Affect All Items>")
	}
	pi.Add(DefID, "(default) <Add An Item>")
	res, err := choosePair(table, *pi)
	if err != nil || res != DefID {
		return res, err
	}
	return db.addPairWtPrompt(table, uniq)
}

// addPairWtPrompt asks user to create an ID - value pair and returns the ID
// Parameter uniq = ID will be auto generated.
func (db Db) addPairWtPrompt(table string, uniq bool) (int, error) {
	var (
		value, id string
		idInt     int
		err       error
	)
	if !uniq {
		id = PromptStr("Enter ID for " + table + "(" + strconv.Itoa(DefID) + " for new item; ID in existence to add alias): ")
		if len(id) < 1 {
			return DefID, ErrInvalidInput
		}
		idInt, err = strconv.Atoi(id)
		if err != nil {
			ShowStrln("Invalid ID. Default taken.")
			idInt = DefID
		} else {
			if idInt == AllID {
				PromptStr(id + " is used for all items. Probably wrong input!")
			}
		}
	}
	value = PromptStr("Enter value for " + table + "(no default value): ")
	if len(value) < 1 {
		return DefID, ErrInvalidInput
	}
	fields := []string{db.fldStr}
	values := []string{value}
	if !uniq {
		if idInt == DefID {
			idInt, err = db.GetMaxID(table)
			if err != nil {
				//no max ID found
				idInt = DefID
			}
			idInt++
			id = strconv.Itoa(idInt)
		}
		fields = append(fields, db.fldID)
		values = append(values, id)
	}
	val, err := db.AddWtParams(table, fields, values, false)
	if err != nil {
		return DefID, err
	}
	if !uniq {
		return idInt, err
	}
	return val, err
}

// GetMaxID gets the largest ID in a table.
func (db Db) GetMaxID(table string) (int, error) {
	// try auto increment
	res, err := db.Search("INFORMATION_SCHEMA.TABLES",
		"TABLE_NAME = \""+table+"\"",
		[]string{"AUTO_INCREMENT"}, "")
	// AND TABLE_SCHEMA = 'yourDatabaseName'
	if err == nil {
		if len(res) > 0 && len(res[0]) > 0 {
			id, err := strconv.Atoi(res[0][0])
			if err == nil {
				return id, nil
			}
		}
	}

	// try max id
	rows, err := db.Query("SELECT MAX(" + db.fldID + ") from " + table)
	defer rows.Close()
	rows.Next()
	var id int
	if err = rows.Scan(&id); err != nil {
		return InvalidID, err
	}
	return id, nil
}

// AddPairNoID adds an item with value, where ID should be auto generated
func (db Db) AddPairNoID(table string, str string) (int, error) {
	return db.AddWtParams(table, []string{db.fldStr}, []string{str}, false)
}

// AddPair adds an item with ID and value
func (db Db) AddPair(table string, id int, str string) (res int, err error) {
	cmd := "INSERT INTO " + table + " (" + db.fldID + ", " + db.fldStr + ") VALUES (\"" + strconv.Itoa(id) + "\", \"" + str + "\")"
	if Debugging {
		if Verbose > 2 {
			LogPrint(cmd)
		}
	}
	_, err = db.Exec(cmd)
	//if err != nil {
	//LogErrPrint(err)
	//}
	res = id
	return
}

// UpdatePairWtParams updates value by ID
func (db Db) UpdatePairWtParams(table string, id, str string) error {
	cmd := "UPDATE " + table + " SET " + db.fldStr + "=\"" +
		str + "\" WHERE " + db.fldID + "=\"" + id + "\""
	if Debugging {
		if Verbose > 2 {
			LogPrint(cmd)
		}
	}
	_, err := db.Exec(cmd)
	//if err != nil {
	//LogErrPrint(err)
	//}
	return err
}

// ShrinkIDs reorganizes table with compacted IDs
// Parameters: fun is called before changing one ID with old and new values
func (db Db) ShrinkIDs(table string, fun func(b4, aft string)) error {
	recs, err := db.Search(table, "", []string{db.fldID},
		" ORDER BY "+db.fldID)
	if err != nil {
		return err
	}
	recInd := DefID
	for _, rec := range recs {
		recStr := strconv.Itoa(recInd)
		if recStr != rec[0] {
			if fun != nil {
				fun(rec[0], recStr)
			}
			if err = db.UpdatePairID(table, rec[0], recStr); err != nil {
				return err
			}
		}
		recInd++
	}
	recStr := strconv.Itoa(recInd)
	cmd := "ALTER TABLE " + table + " AUTO_INCREMENT " + "=" + " " + recStr
	if Debugging {
		if Verbose > 2 {
			LogPrint(cmd)
		}
	}
	_, err = db.Exec(cmd)
	return err
}

// UpdatePairID updates ID
func (db Db) UpdatePairID(table string, idOld, idNew string) error {
	cmd := "UPDATE " + table + " SET " + db.fldID + "=\"" +
		idNew + "\" WHERE " + db.fldID + "=\"" + idOld + "\""
	if Debugging {
		if Verbose > 2 {
			LogPrint(cmd)
		}
	}
	_, err := db.Exec(cmd)
	//if err != nil {
	//LogErrPrint(err)
	//}
	return err
}

// AppUpgrade checks for updates and applies the update automatically,
// which will work next time the app is run.
// Parameters:
// - table not used yet and is tblDef
// - Items with following names and **prefix**, in table, are used to check updates
//   - Url, UrlDev: IP of the update server
//   - Dir: dir name on the server
//   - App: app name on the server
//   - ver: current version
//   - server: better to be 1 buffered. server info returned, if not null
//   - ch: better to be 2 buffered.
//     1st=false, if wrong server URL configured.
//     2nd=false, if other configurations wrong, or update check fails.
func (db Db) AppUpgrade(table, prefix, ver string, server *chan string, ch chan bool) {
	/*type pairs struct {
		id  string
		str string
	}
	upPairs := []pairs{
		{
			"Url", "",
		},
		{
			"Dir", "",
		},
		{
			"App", "",
		},
	}*/
	upPairs := new(Pairs[string, string])
	if ver == "dev" {
		upPairs.Add("UrlDev", "")
	} else {
		upPairs.Add("Url", "")
	}
	upPairs.Add("Dir", "")
	upPairs.Add("App", "")
	var (
		id, upStr string
		err       error
	)
	upPairs.Rewind()
	for err == nil {
		id, _, err = upPairs.GetNMove()
		if err == ErrOutOfBound {
			break
		}
		if err != nil || len(id) < 1 {
			if Debugging {
				LogPrint("NO ID configured!", err)
			}
		}
		upStr, err = db.GetPairStr(db.tblDef, prefix+id)
		if err == nil && len(upStr) > 0 {
			err = upPairs.SetPrev(id, upStr)
			if err != nil {
				if Debugging {
					LogPrint(err)
				}
			}
		} else {
			if Debugging {
				LogPrint("NO " + prefix + id + " configured!")
			}
			if err != nil {
				if Debugging {
					LogPrint(err)
				}
			}
			break
		}
	}
	_, upStr, _ = upPairs.Get(0)
	if len(upStr) < 1 {
		if Debugging && Verbose > 1 {
			Log("NO update server configured")
		}
		ch <- false
		return
	}
	if Debugging && Verbose > 2 {
		Log("update check response temp")
	}
	ch <- true
	if server != nil {
		*server <- upStr
	}
	_, upDir, _ := upPairs.Get(1)
	_, upCmd, _ := upPairs.Get(2)
	if len(upDir) < 1 || len(upCmd) < 1 {
		ch <- false
		if Debugging {
			Log("update check response final failure")
		}
		return
	}
	var updater = &selfupdate.Updater{
		CurrentVersion: ver,
		ApiURL:         upStr,
		BinURL:         upStr,
		DiffURL:        upStr,
		Dir:            upDir,
		CmdName:        upCmd,
		ForceCheck:     true,
	}
	if ver == "ro" || len(ver) < 1 {
		if Verbose > 0 {
			ShowStrln("update skipped for ro or nil versions.")
		}
		ch <- true
		return
	}
	if err = updater.BackgroundRun(); err != nil {
		if Verbose > 0 {
			ShowStrln("Failed to check update for this app")
		}
		Log(err)
		if Debugging && Verbose > 2 {
			Log("update check response final")
		}
		ch <- false
	}
	if len(updater.Info.Version) > 0 && ver != updater.Info.Version {
		if Verbose > 0 {
			ShowStrln("On the next run, this app will be updated to " + updater.Info.Version)
		}
	}
	ch <- true
}

// Authenticate checks whether authenticated.
// Parameters: table not used yet and is tblDef
// Return value: nil if true; ErrNoValidResults if no db config for table name
func (db Db) Authenticate(table, username, password string) error {
	var (
		conn *ldap.Conn
		err  error
	)
	if len(db.tblDef) < 1 {
		return ErrNoValidResults
	}
	ip, err := db.GetPairStr(db.tblDef, "AuthIP")
	if err != nil {
		return err
	}
	part1, err := db.GetPairStr(db.tblDef, "AuthRootD")
	if err != nil {
		return err
	}
	part2, err := db.GetPairStr(db.tblDef, "AuthRootN")
	if err != nil {
		return err
	}
	conn, err = ldap.DialURL("ldap://" + ip)
	if err != nil {
		return err
	}
	err = conn.Bind(part1+username+part2, password)
	conn.Close()
	return err
}
